package com.ruoyi.client.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.stream.CollectorUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.client.constants.RedisConstants;
import com.ruoyi.client.domain.entity.*;
import com.ruoyi.client.domain.vo.EchartsVo;
import com.ruoyi.client.domain.vo.OperVo;
import com.ruoyi.client.domain.vo.RecommendUserVo;
import com.ruoyi.client.mapper.BlogAttentionMapper;
import com.ruoyi.client.mapper.BlogCategoryMapper;
import com.ruoyi.client.mapper.BlogOperMapper;
import com.ruoyi.client.service.*;
import com.ruoyi.common.core.utils.MapstructUtils;
import com.ruoyi.common.core.utils.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class IndexService {
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    IBlogUserService userService;
    @Autowired
    BlogAttentionMapper attentionMapper;
    @Autowired
    BlogCategoryMapper categoryMapper;
    @Autowired
    MongoTemplate mongoTemplate;
    @Autowired
    IBlogArticleService articleService;
    @Autowired
    IBlogOperService operService;
    @Autowired
    BlogOperMapper operMapper;
    @Autowired
    IBlogCommentService commentService;
    public boolean sign(Long id, Integer offset){
        String signKey = RedisConstants.SIGN + ":" + id;
        String signRepairKey = RedisConstants.SIGN_REPAIR + ":" + id;
        String signScoreKey = RedisConstants.SIGNSCORE + ":" + id;
        String signRepairAbleNumKey = RedisConstants.SIGN_REPAIR_ABLE_NUM + ":" + id;
        // 判断是否是补签
        if (offset == LocalDateTime.now().getDayOfMonth()) {
            // 不是补签
            Boolean aBoolean = redisTemplate.opsForValue().setBit(signKey, offset.longValue() - 1l, true);
            String score = redisTemplate.opsForValue().get(signScoreKey);
            if (StringUtils.isNotBlank(score)) {
                redisTemplate.opsForValue().set(signScoreKey, String.valueOf(Integer.parseInt(score) + 5));
            }
            else {
                redisTemplate.opsForValue().set(signScoreKey, String.valueOf(5));
            }
        }
        // 补签
        else {
            Boolean aBoolean = redisTemplate.opsForValue().setBit(signRepairKey, offset.longValue() - 1l, true);
            String score = redisTemplate.opsForValue().get(signScoreKey);
            String signRepairAbleNum = redisTemplate.opsForValue().get(signRepairAbleNumKey);
            if (StringUtils.isNotBlank(score)) {
                redisTemplate.opsForValue().set(signScoreKey, String.valueOf(Integer.parseInt(score) + 1));
                if (StringUtils.isNotBlank(signRepairAbleNum)) {
                    redisTemplate.opsForValue().set(signRepairAbleNumKey, String.valueOf(Integer.parseInt(signRepairAbleNum) - 1));
                }
                else {
                    redisTemplate.opsForValue().set(signRepairAbleNumKey, String.valueOf(3));
                }
            }
            else {
                redisTemplate.opsForValue().set(signScoreKey, String.valueOf(1));
                if (StringUtils.isNotBlank(signRepairAbleNum)) {
                    redisTemplate.opsForValue().set(signRepairAbleNumKey, String.valueOf(Integer.parseInt(signRepairAbleNum) - 1));
                }
                else {
                    redisTemplate.opsForValue().set(signRepairAbleNumKey, String.valueOf(3));
                }
            }
        }
        return true;
    }

    public Map<String, Object> signInfo(Long userId) {
        String signKey = RedisConstants.SIGN + ":" + userId;
        String signRepairKey = RedisConstants.SIGN_REPAIR + ":" + userId;
        String signScoreKey = RedisConstants.SIGNSCORE + ":" + userId;
        Integer offset = LocalDateTime.now().getDayOfMonth();
        String signRepairAbleNumKey = RedisConstants.SIGN_REPAIR_ABLE_NUM + ":" + userId;
        // 是否签到
        Boolean isSign = redisTemplate.opsForValue().getBit(signKey, offset.longValue() - 1);

        // 连续签到数
        int maxConstantNum = getMaxConstantNum(signKey);
        // 签到map
        HashMap<String, Boolean> signMap = new HashMap<>();
        HashMap<Object, Object> repairSignMap = new HashMap<>();
        // 获取月初日期
        LocalDate startOfLastSevenDays = LocalDate.now().withDayOfMonth(1);
        for (int i = 1; i <= offset ; i++) {
            signMap.put(startOfLastSevenDays.toString(), redisTemplate.opsForValue().getBit(signKey,i - 1));
            startOfLastSevenDays = startOfLastSevenDays.plusDays(1);
        }
        startOfLastSevenDays = LocalDate.now().withDayOfMonth(1);
        for (int i = 1; i <= offset ; i++) {
            repairSignMap.put(startOfLastSevenDays.toString(), redisTemplate.opsForValue().getBit(signRepairKey,i - 1));
            startOfLastSevenDays = startOfLastSevenDays.plusDays(1);
        }
        // 签到得分
        String score = redisTemplate.opsForValue().get(signScoreKey);
        // 剩余补签数
        String signRepairAbleNum = redisTemplate.opsForValue().get(signRepairAbleNumKey);
        if (StringUtils.isBlank(signRepairAbleNum)) {
            redisTemplate.opsForValue().set(signRepairAbleNumKey, String.valueOf(3));
            signRepairAbleNum = "3";
        }
        return Map.of("isSign",ObjectUtil.defaultIfNull(isSign,false),
            "maxConstantNum",maxConstantNum,
            "signInfo",signMap,
            "repairSignInfo",repairSignMap,
            "signRepairAbleNum",StringUtils.defaultIfEmpty(signRepairAbleNum,"3"),
            "score", ObjectUtil.defaultIfBlank(score,"0"));
    }

    private int getMaxConstantNum(String key) {
        int totalDay = LocalDate.now().lengthOfMonth();
        int maxConstantsDay = 0;
        int temp = 0;
        for (int i = 0; i < totalDay; i++) {
            Boolean bit = redisTemplate.opsForValue().getBit(key, i);
            if (bit) {
                temp += 1;
            }else {
                temp = 0;
            }
            if (temp > maxConstantsDay)
                maxConstantsDay = temp;
        }
        return maxConstantsDay;
    }

    /**
     * 获取推荐作者信息
     * @param userId
     * @return
     */
    public List<RecommendUserVo> getRecommendUser(Long userId) {
        // 获取推荐作者 todo 后续完善推荐算法
        Page<BlogUser> page = Page.of(1, 6);
        List<BlogUser> userList = userService.list(page, Wrappers.<BlogUser>lambdaQuery()
                .ne(BlogUser::getId,userId)
            .eq(BlogUser::getStatus, 1));
        ArrayList<RecommendUserVo> voArrayList = new ArrayList<>();
        List<Long> attentionUserId = attentionMapper.getAttentionUserId(userId);

        userList.forEach(v -> {
            // 设置是否关注
            RecommendUserVo userVo = new RecommendUserVo();
            BeanUtils.copyProperties(v,userVo);
            if (attentionUserId.contains(v.getId())) {
                userVo.setIsAttention(true);
            }
            else {
                userVo.setIsAttention(false);
            }
            // 查询最近文章
            List<BlogArticle> articleList = articleService.list(Page.of(1, 4)
                , Wrappers.<BlogArticle>lambdaQuery().eq(BlogArticle::getUserId, v.getId()).eq(BlogArticle::getStatus, 4).orderByDesc(BlogArticle::getCreateTime)
            );
            userVo.setLastArticle(articleList);
            voArrayList.add(userVo);
        });
        return voArrayList;
    }

    public Boolean attentionUser(Long userId, Long id, Boolean isAttention) {
        BlogAttention attention = new BlogAttention();
        attention.setUserId(userId);
        attention.setTargetUserId(id);
        if (isAttention) {
            attentionMapper.insert(attention);
            // 设置总关注数
            UpdateWrapper<BlogUser> wrapper = new UpdateWrapper<>();
            wrapper.setSql("total_attention_num = total_attention_num + 1")
                .eq("id",userId);
            userService.update(wrapper);
            //设置对方粉丝数
            UpdateWrapper<BlogUser> fanWrapper = new UpdateWrapper<>();
            fanWrapper.setSql("total_fan_num = total_fan_num + 1")
                .eq("id",id);
            userService.update(fanWrapper);
        }
        else {
            attentionMapper.delete(Wrappers.<BlogAttention>lambdaQuery()
                .eq(BlogAttention::getUserId,userId)
                .eq(BlogAttention::getTargetUserId,id)
            );
            // 设置总关注数
            UpdateWrapper<BlogUser> wrapper = new UpdateWrapper<>();
            wrapper.setSql("total_attention_num = total_attention_num - 1")
                .eq("id",id);
            userService.update(wrapper);
            //设置对方粉丝数
            UpdateWrapper<BlogUser> fanWrapper = new UpdateWrapper<>();
            fanWrapper.setSql("total_fan_num = total_fan_num - 1")
                .eq("id",id);
        }
        return true;
    }

    public List<EchartsVo> getArticleProportion() {
        List<EchartsVo> result = categoryMapper.getArticleProportion();
        return result;
    }

    public List<EchartsVo> getAccessNum() {
        LocalDate now = LocalDate.now();
        LocalDate startDate = now.minusDays(6); // 最近7天（含今天）
        LocalDate endDate = now;

        // 查询 MongoDB 中符合条件的数据
        Criteria criteria = Criteria.where("date").gte(startDate).lte(endDate);
        Query query = Query.query(criteria);
        List<MWatch> mWatches = mongoTemplate.find(query, MWatch.class);

        // 将查询结果按日期分组（Map<LocalDate, Long>）
        Map<LocalDate, Long> dateCountMap = mWatches.stream()
            .collect(Collectors.groupingBy(
                MWatch::getDate,
                Collectors.counting()
            ));

        // 补全缺失的日期，并填充默认值 0
        List<EchartsVo> result = new ArrayList<>();
        for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
            Long count = dateCountMap.getOrDefault(date, 0L);
            result.add(new EchartsVo(date.toString(), count));
        }
        return result;
    }

    public List<OperVo> getLastActivity() {
        List<OperVo> result =operMapper.getLastActivity();
        return result;
    }

    public List<EchartsVo> getTotalWatchNum() {
        List<MWatch> all = mongoTemplate.findAll(MWatch.class);
        if (CollectionUtil.isEmpty(all)) {
            return Collections.emptyList();
        }
        MWatch mWatch = all.stream().reduce(new BinaryOperator<MWatch>() {
            @Override
            public MWatch apply(MWatch mWatch, MWatch mWatch2) {
                mWatch.setCount(mWatch.getCount() + mWatch2.getCount());
                return mWatch;
            }
        }).get();
        System.out.println(mWatch);
        MWatch first = all.get(0);
        MWatch last = all.get(all.size() - 1);

        // 原始数据映射
//        Map<LocalDate, Long> map = all.stream()
//            .collect(Collectors.toMap(MWatch::getDate, MWatch::getCount));

        Map<LocalDate, Long> map = all.stream()
            .collect(Collectors.groupingBy(
                watch -> watch.getDate(), // 转换为LocalDate
                Collectors.counting()));

        ArrayList<EchartsVo> result = new ArrayList<>();

        // 生成日期范围
        LocalDate currentDate = first.getDate();
        LocalDate endDate = last.getDate();

        while (!currentDate.isAfter(endDate)) {
            EchartsVo vo = new EchartsVo();
            vo.setName(currentDate.toString());

            // 如果map中有这个日期的数据，就用实际值，否则用0
            vo.setValue(map.getOrDefault(currentDate, 0L));

            result.add(vo);
            currentDate = currentDate.plusDays(1);  // 正确递增日期
        }

        // 如果需要按日期排序（虽然这里已经是顺序的）
        result.sort(Comparator.comparing(EchartsVo::getName));
        return result;
    }

    public List<EchartsVo> getTotalArticleNum() {
        // 1. 获取已发布的文章列表
        List<BlogArticle> list = articleService.list(
            Wrappers.<BlogArticle>lambdaQuery()
                .eq(BlogArticle::getStatus, 4)
                .orderByDesc(BlogArticle::getCreateTime)
        );

        // 2. 按天统计文章数量
        Map<LocalDate, Long> dailyCountMap = list.stream()
            .filter(article -> article.getCreateTime() != null) // 过滤掉createTime为null的记录
            .collect(Collectors.groupingBy(
                article -> article.getCreateTime().toLocalDate(), // 转换为LocalDate
                Collectors.counting()
            ));

        // 3. 确定日期范围
        if (list.isEmpty()) {
            return Collections.emptyList(); // 没有数据时返回空列表
        }

        // 获取最早和最晚日期
        LocalDate startDate = list.get(list.size()-1).getCreateTime().toLocalDate();
        LocalDate endDate = list.get(0).getCreateTime().toLocalDate();

        // 4. 生成完整的日期序列并补齐缺失日期
        List<EchartsVo> result = startDate.datesUntil(endDate.plusDays(1)) // 生成日期流
            .map(date -> new EchartsVo(
                date.toString(),
                dailyCountMap.getOrDefault(date, 0L) // 补齐缺失日期为0
            ))
            .collect(Collectors.toList());

        // 5. 按日期排序（如果需要）
        result.sort(Comparator.comparing(EchartsVo::getName));
        return result;
    }

    public List<EchartsVo> getCommentNum() {
        List<BlogComment> list = commentService.list(
            Wrappers.<BlogComment>lambdaQuery()
                .orderByDesc(BlogComment::getCreateTime)
        );

        // 2. 按天统计文章数量
        Map<LocalDate, Long> dailyCountMap = list.stream()
            .filter(comment -> comment.getCreateTime() != null) // 过滤掉createTime为null的记录
            .collect(Collectors.groupingBy(
                comment -> comment.getCreateTime().toLocalDate(), // 转换为LocalDate
                Collectors.counting()
            ));

        // 3. 确定日期范围
        if (list.isEmpty()) {
            return Collections.emptyList(); // 没有数据时返回空列表
        }

        // 获取最早和最晚日期
        LocalDate startDate = list.get(list.size()-1).getCreateTime().toLocalDate();
        LocalDate endDate = list.get(0).getCreateTime().toLocalDate();

        // 4. 生成完整的日期序列并补齐缺失日期
        List<EchartsVo> result = startDate.datesUntil(endDate.plusDays(1)) // 生成日期流
            .map(date -> new EchartsVo(
                date.toString(),
                dailyCountMap.getOrDefault(date, 0L) // 补齐缺失日期为0
            ))
            .collect(Collectors.toList());

        // 5. 按日期排序（如果需要）
        result.sort(Comparator.comparing(EchartsVo::getName));
        return result;
    }

    public List<EchartsVo> getUserNum() {
        List<BlogUser> list = userService.list(
            Wrappers.<BlogUser>lambdaQuery()
                .orderByDesc(BlogUser::getCreateTime)
        );

        // 2. 按天统计文章数量
        Map<LocalDate, Long> dailyCountMap = list.stream()
            .filter(comment -> comment.getCreateTime() != null) // 过滤掉createTime为null的记录
            .collect(Collectors.groupingBy(
                comment -> comment.getCreateTime().toLocalDate(), // 转换为LocalDate
                Collectors.counting()
            ));

        // 3. 确定日期范围
        if (list.isEmpty()) {
            return Collections.emptyList(); // 没有数据时返回空列表
        }

        // 获取最早和最晚日期
        LocalDate startDate = list.get(list.size()-1).getCreateTime().toLocalDate();
        LocalDate endDate = list.get(0).getCreateTime().toLocalDate();

        // 4. 生成完整的日期序列并补齐缺失日期
        List<EchartsVo> result = startDate.datesUntil(endDate.plusDays(1)) // 生成日期流
            .map(date -> new EchartsVo(
                date.toString(),
                dailyCountMap.getOrDefault(date, 0L) // 补齐缺失日期为0
            ))
            .collect(Collectors.toList());

        // 5. 按日期排序（如果需要）
        result.sort(Comparator.comparing(EchartsVo::getName));
        return result;
    }
}
